home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-05-23 | 33.5 KB | 1,052 lines | [TEXT/KAHL] |
- // ****************************************************************************
- //
- // Bolo standard Autopilot Brain
- // (C) 1993 Stuart Cheshire <cheshire@cs.stanford.edu>
- // I make no claims that this is good code. It is provided solely as
- // simple example code to assist in writing Bolo plug-in Brain modules.
- // I do not have the time to tidy it up and make it elegant at all, so
- // if you have ever been a student of mine and lost marks for bad style,
- // don't even think of trying to use this code to justify claiming extra
- // marks. It is crap. And it uses far too many global variables.
- //
- // ****************************************************************************
-
- #include <FixMath.h>
- #include "debug.h"
- #include "Brain.h"
-
- // ****************************************************************************
-
- local const BrainInfo *info;
- local long ThinkStart;
- local MAP_X nearx;
- local MAP_Y neary;
- local MAP_X tankleftx, tankrightx;
- local MAP_Y tanktopy, tankbottomy;
- local Boolean tank_close_to_mine;
- local Boolean still_on_boat; // Set until tank gets off boat
- local BYTE land_direction; // Initial guess at where the land is
-
- // If cannot reach a target, get bored and ignore it after 20 seconds
- #define ATTENTION_SPAN (60*20)
- #define MAX_VIS_RANGE 0x1000
- #define DEFENSIVE_RANGE 0xA00
-
- // Keyboard Control variables
- local u_long keys, taps, last_keys, last_taps;
-
- // Menu handling variables
- #define MyMenuID 1000
- local MenuHandle MyMenu;
- local Boolean aggressive = FALSE, laymines = TRUE;
- local Boolean clearmines = TRUE, placepills = TRUE;
-
- // Thinking variables
- local Boolean shootit, runaway;
- local BYTE chosen_gunrange; // Desired gun range, for shooting mines
- local BYTE shoot_direction; // to aim the gun accurately
- local BYTE shoot_dir_vote; // shoot_direction converted to range 0-15
- local BYTE chosen_direction;
- local BYTE flying_shells; // total number of shells in flight at the moment
- local BYTE incoming_shells; // total number of shells heading towards us
- local long direction_votes[16];
- local u_long target_distances[16];
-
- local long boredomtime = ATTENTION_SPAN;
- local long *tankboredom, *pillboredom, *baseboredom;
-
- local u_long current_dist = MAX_VIS_RANGE;
- local long current_attempt_time;
-
- local MAP_X scoutx;
- local MAP_Y scouty;
- #define CAN_DO_BUILDING (info->build->action == 0 && info->man_status == 0 \
- && !info->inboat && flying_shells == 0 && !shootit)
-
- typedef struct { char x; char y; } charpair;
-
- typedef union
- {
- charpair s25[25];
- struct { charpair s1[1]; charpair s8[8]; charpair s16[16]; } s;
- } SURROUNDING_SQUARES;
-
- local const SURROUNDING_SQUARES surrounding =
- {
- 0,0,
- 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, -1,-1,
- 0,-2, 1,-2, 2,-2, 2,-1, 2,0, 2,1, 2,2, 1,2,
- 0,2, -1,2, -2,2, -2,1, -2,0, -2,-1, -2,-2, -1, -2
- };
-
- // ****************************************************************************
-
- // General utility routines
-
- #define CanSee(X,Y) \
- ((X) >= info->view_left && (X) < info->view_left + info->view_width && \
- (Y) >= info->view_top && (Y) < info->view_top + info->view_height )
-
- local TERRAIN raw_getmapcell(MAP_X x, MAP_Y y)
- {
- if (x < info->view_left || y < info->view_top) return(DEEPSEA);
- y -= info->view_top;
- x -= info->view_left;
- if (x >= info->view_width || y >= info->view_height) return(DEEPSEA);
- return(info->viewdata[y*info->view_width + x]);
- }
-
- // These two read the terrain / check for mine at MAP coordinates X,Y
- #define getmapcellM(X,Y) (raw_getmapcell((X),(Y)) & TERRAIN_MASK)
- #define checkmineM(X,Y) (raw_getmapcell((X),(Y)) & TERRAIN_MINE)
-
- // These two read the terrain / check for mine at WORLD coordinates X,Y
- #define getmapcellW(X,Y) getmapcellM((X)>>8, (Y)>>8)
- #define checkmineW(X,Y) checkmineM((X)>>8, (Y)>>8)
-
- local u_long findrange(WORLD_X x1, WORLD_Y y1, WORLD_X x2, WORLD_Y y2)
- {
- register long xdiff = (long)x1 - (long)x2;
- register long ydiff = (long)y1 - (long)y2;
- return(FracSqrt(xdiff*xdiff + ydiff*ydiff) >> 15);
- }
-
- #define objectrange_to_me(OB) \
- findrange((OB)->x, (OB)->y, info->tankx, info->tanky)
-
- // For Mac fixed point trig routines, full circle is 2 * PI * 0x10000
- // We want full circle to be 0x100, so divide by (2 * PI * 0x10000 / 0x100)
- // which is 1608 in decimal
- #define aim(X,Y) ((FixATan2(-(Y),(X))/1608) & 0xFF)
-
- // sin and cos routines are adjusted to take angles in the range 0-255
- // and return return values in the range +/-128
- #define sin(X) ((short)(FracSin((u_char)(X)*1608L) >> 23))
- #define cos(X) ((short)(FracCos((u_char)(X)*1608L) >> 23))
-
- // ****************************************************************************
-
- // Route finding routines
-
- typedef struct // Used to find best route through cost array
- {
- BYTE x; // Location in array
- BYTE y;
- u_short cost; // Cost to reach this square
- } CostPoint;
-
- #define COST_SEARCH_RANGE 14
- // Tank can see the square it is on, and up to COST_SEARCH_RANGE
- // above, below and to either side of it
- #define COST_ARRAY_SIZE (COST_SEARCH_RANGE + 1 + COST_SEARCH_RANGE)
- typedef u_short u_short32[32]; // Row of 32 values
- local u_short32 squarecost[COST_ARRAY_SIZE]; // Array of rows of cost values
- #define UNKNOWN_COST 0xFFFF
- #define MAX_COST (UNKNOWN_COST-1)
- local MAP_X costarray_left; // position of array on map
- local MAP_Y costarray_top;
- local MAP_X costtarget_x; // position of target on map
- local MAP_Y costtarget_y;
- local CostPoint subtarget; // target, or closest point if target is outside costarray
- local long costarray_time; // time array was created
- local BYTE costarray_boat; // Whether costarray calculated for boat or tank
- local BYTE ca_tankx, ca_tanky; // Position of tank in costarray coordinates
-
- #define MAX_HEAP_SIZE 200
- local CostPoint cost_heap[MAX_HEAP_SIZE]; // Sorted heap of CostPoints
- local short heap_size=0;
-
- local void addtoheap(int place, CostPoint new)
- {
- int parent = (place-1)>>1;
- cost_heap[place]=new;
- while (place>0 && cost_heap[parent].cost > cost_heap[place].cost)
- {
- CostPoint temp = cost_heap[place ];
- cost_heap[place ] = cost_heap[parent];
- cost_heap[parent] = temp;
- place=parent;
- parent=(place-1)>>1;
- }
- }
-
- local CostPoint removefromheap(void)
- {
- CostPoint top = cost_heap[0]; // extract top element
- int gap = 0; // top is now empty
- int left = 1; // Left child is element 1
- int right = 2; // Right child is element 2
- heap_size--; // We have removed one element
- while(left<=heap_size) // move gap to bottom
- {
- if(right>heap_size || cost_heap[right].cost > cost_heap[left].cost)
- { cost_heap[gap]=cost_heap[left ]; gap=left; }
- else{ cost_heap[gap]=cost_heap[right]; gap=right; }
- left = (gap<<1)+1;
- right = (gap<<1)+2;
- }
- if(gap != heap_size) addtoheap(gap,cost_heap[heap_size]);
- return(top); // return extracted element
- }
-
- local void setcost(CostPoint c)
- {
- Boolean already_present = (squarecost[c.y][c.x] < UNKNOWN_COST);
- squarecost[c.y][c.x] = c.cost;
-
- if (already_present)
- {
- int i;
- //DebugStr("\pUpdating location already in heap");
- for (i=0; i<heap_size; i++)
- if (cost_heap[i].x == c.x && cost_heap[i].y == c.y) break;
- //if (i >= heap_size) DebugStr("\pFailed to find in heap");
- addtoheap(i, c);
- }
- // If heap not full add new element
- else if (heap_size < MAX_HEAP_SIZE) addtoheap(heap_size++, c);
- // else overwrite last element in heap
- else addtoheap(heap_size-1, c);
- }
-
- // Costs are roughly related to speed, but biased for other properties
- // eg Water is very bad because it loses shells, and forest is nicer than
- // its speed would suggest because of its cloaking property.
- local u_short route_square_costs[NUM_TERRAINS] =
- {
- 60, 60, 8, 8, // BUILDING, RIVER, SWAMP, CRATER
- 1, 3, 8, 2, // ROAD, FOREST, RUBBLE, GRASS,
- 60, 1, 8, 0, 1000 // HALFBUILDING, BOAT, DEEPSEA, REFBASE_T, PILLBOX_T
- };
-
- local void examine(CostPoint c)
- {
- MAP_X x = costarray_left + c.x;
- MAP_Y y = costarray_top + c.y;
- TERRAIN t = raw_getmapcell(x,y);
- long newcost = c.cost + route_square_costs[t & TERRAIN_MASK];
-
- if (t & TERRAIN_MINE)
- {
- // If clearing mines, cost is related to shells, else very high cost
- if (clearmines && info->shells) newcost += 40-info->shells;
- else newcost += 400;
- }
-
- // Limit cost to stop it overflowing
- if (newcost > MAX_COST) newcost = MAX_COST;
-
- if (c.x < COST_ARRAY_SIZE && c.y < COST_ARRAY_SIZE &&
- squarecost[c.y][c.x] > newcost) { c.cost = newcost; setcost(c); }
- }
-
- local void make_costarray(MAP_X tx, MAP_Y ty)
- {
- int x,y;
- u_short bld_cost = 1000;
-
- // Record info about this cost array
- costarray_left = info->view_left;
- costarray_top = info->view_top;
- costtarget_x = tx;
- costtarget_y = ty;
- costarray_time = TickCount();
- costarray_boat = info->inboat;
-
- if (info->shells > 4) bld_cost = 60 - info->shells;
- route_square_costs[BUILDING ] = bld_cost;
- route_square_costs[HALFBUILDING] = bld_cost;
- route_square_costs[RIVER ] = info->inboat ? 1 : 60;
- route_square_costs[DEEPSEA ] = info->inboat ? 8 : 1000;
-
- // And update tank local coordinates
- ca_tankx = nearx - costarray_left;
- ca_tanky = neary - costarray_top;
-
- // Initialize every square to max cost
- for (y=0; y<COST_ARRAY_SIZE; y++)
- for (x=0; x<COST_ARRAY_SIZE; x++) squarecost[y][x] = UNKNOWN_COST;
-
- // Start from target, with zero cost
- if (tx < costarray_left) subtarget.x = 0; else subtarget.x = tx - costarray_left;
- if (ty < costarray_top ) subtarget.y = 0; else subtarget.y = ty - costarray_top;
- if (subtarget.x > COST_ARRAY_SIZE - 1) subtarget.x = COST_ARRAY_SIZE - 1;
- if (subtarget.y > COST_ARRAY_SIZE - 1) subtarget.y = COST_ARRAY_SIZE - 1;
- subtarget.cost = 0;
-
- // Check that tank falls within our visible region
- if (ca_tankx >= COST_ARRAY_SIZE ||
- ca_tanky >= COST_ARRAY_SIZE) return;
-
- heap_size = 0;
- setcost(subtarget);
- while (heap_size)
- {
- CostPoint c = removefromheap();
- // Exit when we find a path to the tank
- if (c.x == ca_tankx && c.y == ca_tanky) return;
-
- // otherwise, continue the search outwards
- c.y--; examine(c); c.y++;
- c.x++; examine(c); c.x--;
- c.y++; examine(c); c.y--;
- c.x--; examine(c);
- }
- }
-
- local void find_best_route(MAP_X tx, MAP_Y ty)
- {
- int i, step = 1;
- long bestx, besty;
- u_short bestcost = MAX_COST;
- static MAP_X nodiagx; // Don't consider diagonal movement
- static MAP_Y nodiagy; // when on this square
-
- // If tank stuck in maze, or very close to a mine, then
- // cutting corners with diagonal travel is not a good idea.
- // Should proceed very cautiously, slowly, from square to square
- if (info->tankobstructed || tank_close_to_mine)
- { nodiagx = nearx; nodiagy = neary; }
- if (nodiagx == nearx && nodiagy == neary) step = 2;
-
- ca_tankx = nearx - costarray_left;
- ca_tanky = neary - costarray_top;
-
- // If array is old, or for the wrong target, make a new one
- if ((long)TickCount() - costarray_time > 120 ||
- costtarget_x != tx || costtarget_y != ty ||
- costarray_boat != info->inboat) make_costarray(tx,ty);
-
- for (i=0; i<8; i+=step)
- {
- BYTE x = ca_tankx+surrounding.s.s8[i].x;
- BYTE y = ca_tanky+surrounding.s.s8[i].y;
- if (x < COST_ARRAY_SIZE &&
- y < COST_ARRAY_SIZE && bestcost > squarecost[y][x])
- {
- bestcost = squarecost[y][x];
- bestx = ((WORLD_X)(x + costarray_left)<<8) + 0x80;
- besty = ((WORLD_Y)(y + costarray_top )<<8) + 0x80;
- }
- }
- i = aim(bestx - (long)info->tankx, besty - (long)info->tanky);
- direction_votes[i + 8 >> 4 & 0xF] += 100;
- }
-
- // ****************************************************************************
-
- // Code to think about moving objects on the screen
-
- local void reset_boredom(u_long dist)
- {
- current_dist = dist; // We are now this far from our target
- current_attempt_time = (long)TickCount();
- // Keep our attention on this target for a while more
- }
-
- local u_long check_objects(void)
- {
- int i;
- long tx, ty, dummy;
- long *boredomtarget = &dummy;
- ObjectInfo *ob;
- ObjectInfo *nearest_tank = NULL;
- ObjectInfo *nearest_pill = NULL, *nearest_dead = NULL;
- ObjectInfo *nearest_base = NULL, *nearest_refuel = NULL;
- u_long dist, max_dist = aggressive ? MAX_VIS_RANGE : DEFENSIVE_RANGE;
- u_long tankd = max_dist, pilld = max_dist, deadd = max_dist;
- u_long based = max_dist, refueld = max_dist;
- Boolean decided = FALSE;
- BYTE wanted_shells = 20, useful_shells = 6;
-
- // If we are on a refuelling base then we want full shells, and we wait
- // until we have got every last shell from the base, otherwise we
- // will put up with as few as 20 for general trundling around the map
- // and not be attracted by bases that have less than 6 shells to give.
- if (getmapcellW(info->tankx, info->tanky) == REFBASE_T)
- { wanted_shells = 40; useful_shells = 1; }
-
- if (info->base) // If we have some information about a nearby base
- {
- // If base has nothing to give, mark it as "boring"
- if (info->base_shells == 0 && info->base_armour <= MIN_BASE_ARMOUR)
- baseboredom[info->base->idnum] = (long)TickCount() + boredomtime;
-
- // If interested in base, make it the "nearest_refuel"
- if (info->shells < wanted_shells &&
- info->base_shells >= useful_shells &&
- info->base_armour > MIN_BASE_ARMOUR)
- {
- nearest_refuel = info->base;
- refueld = objectrange_to_me(nearest_refuel);
- }
- }
-
- for (ob=&info->objects[0]; ob<&info->objects[info->num_objects]; ob++)
- {
- switch (ob->object)
- {
- case OBJECT_TANK:
- if((long)TickCount() - tankboredom[ob->idnum] > 0)
- if (ob->info & OBJECT_HOSTILE)
- if ((dist = objectrange_to_me(ob)) < tankd)
- { nearest_tank = ob; tankd = dist; }
- break;
- case OBJECT_PILLBOX:
- if((long)TickCount() - pillboredom[ob->idnum] > 0)
- {
- if (ob->direction == 0) // If pillbox dead
- {
- if ((dist = objectrange_to_me(ob)) < deadd)
- { nearest_dead = ob; deadd = dist; }
- }
- else if (ob->info & OBJECT_HOSTILE)
- {
- if ((dist = objectrange_to_me(ob)) < pilld)
- { nearest_pill = ob; pilld = dist; }
- }
- }
- break;
- case OBJECT_REFBASE:
- if((long)TickCount() - baseboredom[ob->idnum] > 0)
- {
- if (ob->info & OBJECT_HOSTILE)
- {
- if (info->shells || (ob->info & OBJECT_NEUTRAL))
- if ((dist = objectrange_to_me(ob)) < based)
- { nearest_base = ob; based = dist; }
- }
- else
- {
- if ((dist = objectrange_to_me(ob)) < refueld)
- { nearest_refuel = ob; refueld = dist; }
- }
- }
- break;
- }
- }
-
- // Special priority: don't leave man outside tank
- if (info->man_status > 1 && info->manobstructed)
- {
- tx = info->man_x;
- ty = info->man_y;
- dist = findrange(tx, ty, info->tankx, info->tanky);
- if (dist > 0x100) decided = TRUE;
- }
-
- // First priority: hostile tanks/pillboxes which are within shooting range
- if (!decided && (tankd < max_dist || pilld < max_dist))
- {
- Boolean wants_fight = info->shells > 5 &&
- info->armour > incoming_shells + 2;
- if (tankd < pilld) // We have a hostile tank approaching
- {
- dist = tankd;
- tx = nearest_tank->x;
- ty = nearest_tank->y;
- boredomtarget = &tankboredom[nearest_tank->idnum];
- }
- else // Nearest thing is a hostile pillbox
- {
- dist = pilld;
- tx = nearest_pill->x;
- ty = nearest_pill->y;
- boredomtarget = &pillboredom[nearest_pill->idnum];
- // pacifists don't attack pillboxes
- if (!aggressive) wants_fight = FALSE;
- }
-
- // If within shooting range, must make a decision now.
- // Otherwise if we want a fight, and there is no dead
- // pillbox to pick up, attack this tank or pillbox.
- if (dist < 0x800 || (wants_fight && deadd >= max_dist))
- {
- decided = TRUE;
- if (!wants_fight) runaway = TRUE;
- else if (dist < 0x800 && info->shells) shootit = TRUE;
- }
- }
-
- // Second priority: Pick up dead pillboxes
- if (!decided && deadd < max_dist)
- {
- dist = deadd;
- tx = nearest_dead->x;
- ty = nearest_dead->y;
- boredomtarget = &pillboredom[nearest_dead->idnum];
- decided = TRUE;
- }
-
- // Second priority: Capture some enemy bases
- if (!decided && aggressive && based < max_dist)
- {
- dist = based;
- tx = nearest_base->x;
- ty = nearest_base->y;
- boredomtarget = &baseboredom[nearest_base->idnum];
- decided = TRUE;
- if (dist < 0x800 && !(nearest_base->info & OBJECT_NEUTRAL) &&
- info->shells) shootit = TRUE;
- }
-
- // Third priority: Do we need to refuel?
- if (!decided && refueld < max_dist &&
- (info->shells < wanted_shells || info->armour < 8))
- {
- dist = refueld;
- tx = nearest_refuel->x;
- ty = nearest_refuel->y;
- boredomtarget = &baseboredom[nearest_refuel->idnum];
- // If we're on base; don't get bored
- if (dist < 0x80)
- {
- reset_boredom(MAX_VIS_RANGE);
- for (i=0; i<16; i++) target_distances[i] = 0;
- }
- decided = TRUE;
- }
-
- // If not decided on anything, then there is nothing to be bored with
- if (!decided)
- {
- if (!aggressive) // If not aggressive, don't roam around
- for (i=0; i<16; i++) target_distances[i] = 0;
- reset_boredom(MAX_VIS_RANGE);
- return(MAX_VIS_RANGE);
- }
-
- // Still getting closer, so reset current_attempt_time
- if (current_dist > dist) reset_boredom(dist);
- else if ((long)TickCount() - current_attempt_time > ATTENTION_SPAN)
- {
- *boredomtarget = (long)TickCount() + boredomtime;
- reset_boredom(MAX_VIS_RANGE);
- }
-
- //if (info->tankobstructed && !info->shells) return(dist);
- // Don't want this any more -- find_best_route works even if obstructed
-
- shoot_direction = aim(tx - (long)info->tankx, ty - (long)info->tanky);
- shoot_dir_vote = shoot_direction + 8 >> 4 & 0xF;
- if (runaway)
- {
- reset_boredom(MAX_VIS_RANGE); // Don't get bored while running away!
- for (i=-4; i<=4; i++) direction_votes[shoot_dir_vote+i & 0xF] -= 20;
- for (i=5; i<=11; i++) direction_votes[shoot_dir_vote+i & 0xF] += 20;
- }
- else
- {
- target_distances[shoot_dir_vote] = dist;
- if (shootit || dist < 0x100) // If shooting or VERY close to target
- { direction_votes[shoot_dir_vote] += 100; reset_boredom(dist); }
- else { find_best_route(tx>>8, ty>>8); return(0); }
- // Return zero to override normal terrain evaluation
- }
- return(dist);
- }
-
- // ****************************************************************************
-
- // Code to actively explore the map for things to do
-
- local void setscout(void)
- {
- // Pick a new location to head towards
- scoutx = 53 + 10 * (TickCount() & 15);
- scouty = 53 + 10 * (TickCount()>>4 & 15);
- }
-
- local void scout(void)
- {
- u_long dist = findrange((WORLD_X)scoutx << 8, (WORLD_Y)scouty << 8,
- info->tankx, info->tanky);
- // If still getting closer, reset current_attempt_time
- if (current_dist > dist) reset_boredom(dist);
- // else see if we should pick another target
- else if ((long)TickCount() - current_attempt_time > ATTENTION_SPAN)
- {
- reset_boredom(MAX_VIS_RANGE);
- setscout();
- }
-
- if (nearx == scoutx && neary == scouty) setscout();
-
- find_best_route(scoutx, scouty);
-
- if (getmapcellM(costarray_left + subtarget.x, costarray_top + subtarget.y) == DEEPSEA)
- setscout();
- }
-
- // ****************************************************************************
-
- // Code to think about what kind of terrain we should try to get/stay on
- // Simple kind of insect behaviour -- just look at the nearby terrain and
- // decide which is nicest
-
- local long terrain_desirability[NUM_TERRAINS] =
- {
- -10, -2, -1, -1, // BUILDING, RIVER, SWAMP, CRATER
- 5, 0, -1, 0, // ROAD, FOREST, RUBBLE, GRASS,
- -10, 1, -100, 0, -10// HALFBUILDING, BOAT, DEEPSEA, REFBASE_T, PILLBOX_T
- };
-
- #define desirability(X,Y) \
- (terrain_desirability[getmapcellM((X),(Y))] + (checkmineM((X),(Y)) ? -500 : 0))
-
- local void check_terrain(void)
- {
- int i;
- long des = desirability(nearx, neary);
- terrain_desirability[RIVER ] = info->inboat ? 0 : -2;
- terrain_desirability[DEEPSEA] = info->inboat ? -10 : -100;
- for (i=0; i<8; i++)
- {
- long val = desirability(nearx+surrounding.s.s8[i].x,
- neary+surrounding.s.s8[i].y) - des;
- direction_votes[i*2-2 & 0xF] += val;
- direction_votes[i*2-1 & 0xF] += val*2;
- direction_votes[i*2 ] += val*5;
- direction_votes[i*2+1 ] += val*2;
- direction_votes[i*2+2 & 0xF] += val;
- }
-
- for (i=0; i<16; i++)
- {
- long val = desirability(nearx+surrounding.s.s16[i].x,
- neary+surrounding.s.s16[i].y) - des;
- direction_votes[i-1 & 0xF] += val>>1;
- direction_votes[i ] += val;
- direction_votes[i+1 & 0xF] += val>>1;
- }
-
- // If on road, try to keep tank centred.
- if (getmapcellM(nearx,neary) == ROAD)
- {
- Boolean go_down = getmapcellW(info->tankx, info->tanky - 0x60) != ROAD;
- Boolean go_up = getmapcellW(info->tankx, info->tanky + 0x60) != ROAD;
- Boolean go_right = getmapcellW(info->tankx - 0x60, info->tanky) != ROAD;
- Boolean go_left = getmapcellW(info->tankx + 0x60, info->tanky) != ROAD;
- if (go_down + go_up + go_right + go_left == 1)
- {
- BYTE dir = go_right * 4 + go_down * 8 + go_left * 12;
- for (i=-3; i<=3; i++) direction_votes[dir+i & 0xF] += 10;
- }
- }
- }
-
- // ****************************************************************************
-
- // Code to organize voting about which direction we should be going in
-
- local void cast_votes(void)
- {
- u_long target_distance;
- BYTE d = info->direction + 8 >> 4 & 0xF;
-
- // If we are clearing mines, and we are dangerously close
- // to a mine right now, should just try to get off it so
- // that we get a chance for a safe shot at it.
- if (clearmines && info->shells && tank_close_to_mine)
- { check_terrain(); return; }
-
- // Bias towards continuing in the same direction
- direction_votes[d-1 & 0xF] += 1;
- direction_votes[d+0 & 0xF] += 2;
- direction_votes[d+1 & 0xF] += 1;
- direction_votes[d+7 & 0xF] -= 5;
- direction_votes[d+8 & 0xF] -=10;
- direction_votes[d+9 & 0xF] -= 5;
-
- target_distance = check_objects();
-
- // If only one square between us and the hostile,
- // then terrain doesn't matter any more
- if (target_distance < 0x180) return;
-
- // If nothing within sight, and aggressive, then explore
- if (target_distance == MAX_VIS_RANGE && aggressive) scout();
- else // else just make local decision
- {
- if (still_on_boat) direction_votes[land_direction>>4] += 20;
- check_terrain();
- }
- }
-
- // ****************************************************************************
-
- // Code to build bridges etc where necessary. Returns TRUE if decided to build
-
- local Boolean decide_building(MAP_X x, MAP_Y y)
- {
- TERRAIN m = raw_getmapcell(x, y);
- if (m & TERRAIN_MINE) return;
- info->build->x = x;
- info->build->y = y;
- switch (m & TERRAIN_MASK)
- {
- case RIVER :
- case SWAMP :
- case CRATER :
- case RUBBLE : if (info->trees >= 2)
- info->build->action = BUILDMODE_ROAD;
- break;
- case FOREST : if (info->trees < 40)
- info->build->action = BUILDMODE_FARM;
- break;
- }
- return(info->build->action != 0);
- }
-
- // ****************************************************************************
-
- // Code to clear mines from in front of the tank
-
- local MAP_X shotminex; // Mine we have just shot at -- so ignore it
- local MAP_Y shotminey; // when looking for other mines to shoot
- local long shotminetime; // because it will be gone in a moment.
-
- local MAP_X currentminex; // The mine we plan to shoot at next
- local MAP_Y currentminey;
-
- // Don't even consider trying to hit mines more than 0x60 from the centre.
- #define MINE_HIT_ERROR_LIMIT 0x60
-
- local void minesweep(Boolean *foundmine, Boolean *doshoot)
- {
- char a, best_a;
- short r, best_r = 0;
-
- // Want to try to hit mines as close to the centre as possible.
- BYTE best_hit = MINE_HIT_ERROR_LIMIT;
-
- // If we already have something to shoot at, only worry about nearby mines
- short max_range = shootit ? 6 : 12;
-
- if ((long)TickCount() - shotminetime > 60) shotminex = 0;
- // After shooting at a mine, if, for some unknown reason, it is not
- // gone within one second, then we should take another shot at it
-
- for (a = -32; a<=32; a+=8) // sweep left and right of where tank is going
- {
- Boolean through_forest = FALSE;
- for (r=2; r<max_range; r++) // check up to max_range in front of tank
- {
- WORLD_X wx = info->tankx + sin(chosen_direction + a) * r;
- WORLD_Y wy = info->tanky - cos(chosen_direction + a) * r;
- MAP_X x = wx >> 8; // x,y are square we are checking
- MAP_Y y = wy >> 8;
- TERRAIN m = raw_getmapcell(x, y);
- TERRAIN t = m & TERRAIN_MASK;
-
- // Don't try to shoot through buildings or pillboxes
- if (t == BUILDING || t == HALFBUILDING || t == PILLBOX_T) break;
-
- if (m & TERRAIN_MINE) // If there is a mine on this square...
- {
- // Calculate how far from centre of square the shell will land
- BYTE dx = wx & 0xFF; // dx,dy indicate position within square
- BYTE dy = wy & 0xFF;
- if (dx > 0x80) dx = dx-0x80; else dx = 0x80-dx;
- if (dy > 0x80) dy = dy-0x80; else dy = 0x80-dy;
- if (dx < dy) dx = dy;
-
- if (dx < MINE_HIT_ERROR_LIMIT) // We want to shoot this
- {
- // Don't shoot the mine if we are so close it would damage us
- if ((x == tankleftx || x == tankrightx) &&
- (y == tanktopy || y == tankbottomy)) continue;
-
- // If there is a mine very close to us, then take note of it
- // even if we can't (or don't want to) shoot it immediately.
- if (r<4) *foundmine = TRUE;
-
- // If we have already shot at the mine, ignore it (for now).
- if (x == shotminex && y == shotminey) continue;
-
- // See if this is our best chance at a clean hit
- if (best_hit > dx)
- {
- best_hit = dx;
- best_r = r;
- best_a = a;
- // If firing through forest, we need multiple shots
- // so don't assume shot will hit the mine first time
- if (through_forest) currentminex = 0;
- else { currentminex = x; currentminey = y; }
- }
- }
- }
-
- // If shot would pass through forest, make a note of the fact
- if (t == FOREST) through_forest = TRUE;
-
- // If we are on a boat, we can't shoot past the shore
- if (info->inboat && t != RIVER && t != DEEPSEA) break;
- }
- }
-
- if (best_r)
- {
- chosen_gunrange = best_r;
- chosen_direction = chosen_direction + best_a;
- *foundmine = *doshoot = TRUE;
- }
- }
-
- // ****************************************************************************
-
- // Code to count the votes and decide what action to take
-
- local void count_votes(void)
- {
- int i, best = 0;
- u_long target_distance;
- u_long desired_dist = 0x20; // Approach bases gently, and don't overshoot
- char correction = 0;
- Boolean panic =
- (getmapcellM(nearx,neary) == RIVER || info->inboat || runaway);
- Boolean gofaster = FALSE, goslower = FALSE;
- Boolean fullstop = FALSE, killmine = FALSE;
-
- for (i=1; i<16; i++)
- if (direction_votes[best] < direction_votes[i]) best = i;
-
- if (best == shoot_dir_vote) chosen_direction = shoot_direction;
- else { chosen_direction = best<<4; shootit = FALSE; }
- target_distance = target_distances[best];
-
- // If we have some shells, and we are not on a mine,
- // then think about clearing mines
- if (clearmines && info->shells && !checkmineM(nearx,neary))
- minesweep(&killmine, &shootit);
-
- if (target_distance > desired_dist || killmine)
- correction = (char)(info->direction - chosen_direction);
-
- // Decide whether to turn, or possibly shoot
- if (correction < -9) setkey(keys, KEY_turnright);
- else if (correction > 9) setkey(keys, KEY_turnleft);
- else if (correction < -1) setkey(taps, KEY_turnright);
- else if (correction > 1) setkey(taps, KEY_turnleft);
- else if (shootit && info->gunrange == chosen_gunrange)
- { // OK, we are on target, and we want to shoot
- if (!killmine) { setkey(keys, KEY_shoot); desired_dist = 0x680; }
- else
- {
- // Want to make sure tank is not turning, adjusting range etc.
- // to be sure we will hit the mine and not waste a shell
- u_long testkeys = 1<<KEY_turnleft | 1<<KEY_turnright
- | 1<<KEY_morerange | 1<<KEY_lessrange
- | 1<<KEY_shoot;
- if ((testkeys & (last_keys | last_taps)) == 0)
- {
- // Tap shoot key, to fire ONE shot at the mine
- setkey(taps, KEY_shoot);
- shotminex = currentminex;
- shotminey = currentminey;
- shotminetime = TickCount();
- }
- }
- }
- // Decide whether we should be building or farming here
- if (CAN_DO_BUILDING) decide_building(nearx,neary);
-
- // Decide whether to speed up or slow down, based on whether the
- // tank is facing in the right direction or not. If the tank is
- // facing in the right direction, see if it would be helpful to
- // build road or bridge in front of us
- if (correction < -32 || correction > 32) goslower = TRUE;
- else
- {
- MAP_X examinex = info->tankx + sin(info->direction) >> 8;
- MAP_Y examiney = info->tanky - cos(info->direction) >> 8;
- TERRAIN t = getmapcellM(examinex, examiney);
- if (!info->inboat && t == DEEPSEA) fullstop = TRUE;
- if (CAN_DO_BUILDING)
- {
- for (i=2; i<=3; i++)
- {
- if (decide_building(examinex,examiney)) break;
- examinex = info->tankx + sin(info->direction)*i >> 8;
- examiney = info->tanky - cos(info->direction)*i >> 8;
- }
- }
-
- if (CAN_DO_BUILDING && placepills && info->carriedpills && info->trees >= 4)
- {
- // If carrying pillbox, drop it just behind the tank
- // Can place pillbox on swamp, crater, road, rubble and grass
- static Boolean can_build[NUM_TERRAINS] = { 0,0,1,1,1,0,1,1, 0,0,0,0,0 };
- examinex = info->tankx - sin(info->direction)*2 >> 8;
- examiney = info->tanky + cos(info->direction)*2 >> 8;
- t = raw_getmapcell(examinex, examiney);
- if (!(t & TERRAIN_MINE) && can_build[t & TERRAIN_MASK])
- {
- info->build->x = examinex;
- info->build->y = examiney;
- info->build->action = BUILDMODE_PBOX;
- }
- }
-
- if (target_distance > desired_dist + (info->speed<<4)) gofaster = TRUE;
- if (target_distance < desired_dist + (info->speed<<3)) goslower = TRUE;
- // If we want to go, but we are obstructed, then shoot our way out
- if ((gofaster || panic) && info->tankobstructed && info->speed < 1)
- setkey(keys, KEY_shoot);
- }
-
- // And act on that decision
- if (fullstop || killmine) setkey(keys, KEY_slower);
- else if (panic) setkey(keys, KEY_faster);
- else
- {
- if (gofaster) setkey(taps, KEY_faster);
- if (goslower) setkey(keys, KEY_slower);
- }
-
- // Aggressive tanks are vandalistic when running away
- if (runaway && laymines) setkey(keys, KEY_dropmine);
-
- // Decide whether to adjust gun range
- if (info->gunrange > chosen_gunrange+2) setkey(keys, KEY_lessrange);
- else if (info->gunrange < chosen_gunrange-2) setkey(keys, KEY_morerange);
- else if (info->gunrange > chosen_gunrange ) setkey(taps, KEY_lessrange);
- else if (info->gunrange < chosen_gunrange ) setkey(taps, KEY_morerange);
- }
-
- // ****************************************************************************
-
- // Interface code to communicate with Bolo
-
- local void brain_think(void)
- {
- int i;
- ObjectInfo *ob;
-
- // Map square tank is on now
- nearx = info->tankx >> 8;
- neary = info->tanky >> 8;
-
- // Map squares tank is touching
- tankleftx = info->tankx - 0x80 >> 8;
- tankrightx = info->tankx + 0x80 >> 8;
- tanktopy = info->tanky - 0x80 >> 8;
- tankbottomy = info->tanky + 0x80 >> 8;
-
- // See if tank is dangerously close to a mine
- tank_close_to_mine =checkmineM(tankleftx, tanktopy ) ||
- checkmineM(tankrightx, tanktopy ) ||
- checkmineM(tankleftx, tankbottomy) ||
- checkmineM(tankrightx, tankbottomy);
-
- if (info->newtank)
- {
- reset_boredom(MAX_VIS_RANGE);
- for (i=0; i<info->max_players; i++) tankboredom[i] = ThinkStart;
- for (i=0; i<info->max_pillboxes; i++) pillboredom[i] = ThinkStart;
- for (i=0; i<info->max_refbases; i++) baseboredom[i] = ThinkStart;
- setscout();
- still_on_boat = TRUE; // Tank starts on boat
- land_direction = info->direction;
- }
-
- if (!info->inboat) still_on_boat = FALSE; // Tank not on boat any more
-
- last_keys = keys;
- last_taps = taps;
- keys = taps = 0;
- shootit = runaway = FALSE;
- chosen_gunrange = MAXRANGE;
-
- // Count all shells, and count how many are coming towards us.
- incoming_shells = flying_shells = 0;
- for (ob=&info->objects[0]; ob<&info->objects[info->num_objects]; ob++)
- if (ob->object == OBJECT_SHOT)
- {
- char dev = ob->direction - aim( (long)info->tankx - (long)ob->x,
- (long)info->tanky - (long)ob->y );
- if (dev > -32 && dev < 32) incoming_shells++;
- flying_shells++;
- }
-
- for (i=0; i<16; i++)
- {
- direction_votes[i] = 0;
- target_distances[i] = 0xFFFFFFFF;
- }
-
- cast_votes();
- count_votes();
-
- *(info->holdkeys) = keys;
- *(info->tapkeys ) = taps;
- }
-
- local Boolean brain_open(void)
- {
- int i;
- //const u_char msg[] = "\pHello, I'll be your autopilot for today's drive";
- tankboredom = (long *)NewPtr(sizeof(long) * info->max_players);
- pillboredom = (long *)NewPtr(sizeof(long) * info->max_pillboxes);
- baseboredom = (long *)NewPtr(sizeof(long) * info->max_refbases);
- InsertMenu(MyMenu = GetMenu(MyMenuID), 0);
- DrawMenuBar();
- CheckItem(MyMenu, 1, aggressive);
- CheckItem(MyMenu, 2, laymines );
- CheckItem(MyMenu, 3, clearmines);
- CheckItem(MyMenu, 4, placepills);
-
- // Initialize all the destination bits to zero
- // for (i=0; i<(info->max_players+31)/32; i++) info->messagedest[i] = 0;
-
- // for (i=0; i<info->max_players; i++) info->messagedest[i>>5] |= 1<<(i&31);
- // info->messagedest[info->player_number>>5] |= 1<<(info->player_number&31);
-
- // Copy the message
- // for (i=0; i<=msg[0]; i++) info->sendmessage[i] = msg[i];
-
- return(tankboredom && pillboredom && baseboredom);
- }
-
- local void brain_close(void)
- {
- if (tankboredom) DisposPtr((Ptr)tankboredom);
- if (pillboredom) DisposPtr((Ptr)pillboredom);
- if (baseboredom) DisposPtr((Ptr)baseboredom);
- DeleteMenu(MyMenuID);
- ReleaseResource((Handle)MyMenu);
- DrawMenuBar();
- }
-
- local short brain_menu(short item)
- {
- switch (item)
- {
- case 1 : CheckItem(MyMenu, 1, aggressive ^= 1); return(0);
- case 2 : CheckItem(MyMenu, 2, laymines ^= 1); return(0);
- case 3 : CheckItem(MyMenu, 3, clearmines ^= 1); return(0);
- case 4 : CheckItem(MyMenu, 4, placepills ^= 1); return(0);
- default: return(-1);
- }
- }
-
- pascal short main(const BrainInfo *braininfo)
- {
- if (braininfo->InfoVersion != CURRENT_BRAININFO_VERSION) return(-1);
-
- info = braininfo; // copy parameter into global variable
- ThinkStart = TickCount(); // so we know how long we have been in this call
-
- switch (info->operation)
- {
- case BRAIN_OPEN : if (brain_open()) return(0);
- else brain_close(); return(-1);
- case BRAIN_CLOSE : brain_close(); return(0);
- case BRAIN_THINK : brain_think(); return(0);
- case MyMenuID : return(brain_menu(info->menu_item));
- default : return(-1);
- }
-
- }
-